/**
 *  \file I3ClientContext.cpp
 *  \brief  The I3ClientContext class keeps track of some context information associated with
 *  the i3 overlay in general, not any specific connection.
 */

#ifndef _WIN32
	#include <sys/types.h>
	#include <sys/socket.h>
	#include <arpa/inet.h>
	#include <sys/time.h>
#else 
	#include "netwrap.h"
#endif


#include <errno.h>    
#include <pthread.h>


extern "C" {

    #include "i3.h"
    #include "i3_fun.h"
    #include "i3_client_api_ctx.h"
    #include "i3_client_api.h"

    #include "i3_client_callback.h"
    #include "i3_client_trigger.h"

    #include "i3_client_context.h"

    #include "gen_utils.h"

    //#include "original/i3_client/ping_thread.h"
    //#include "original/i3_client/i3server_list.h"

    #include "i3_debug.h"

    //#include "aes.h"
}

#include "I3Id.h"
#include "I3Packet.h"
#include "I3Trigger.h"
#include "I3ClientContext.h"
#include "I3OverlayNetwork.h"
#include "I3OCDStateInfo.h"
#include "I3Functor.h"
#include "I3TimerEvent.h"

#include "OCD_include.h"

#include "i3CommonHeaders.h"

void dummy_add_ack_timeout(cl_trigger *t, void *data, void* fun_ctx);
void dummy_add_refresh_timeout(cl_trigger *t, void *data, void* fun_ctx);

I3ClientContext::I3ClientContext(I3OverlayNetwork *i3Overlay, const char *basicI3ConfigFileName) {
   
    int returnCode; 
    
    this->i3Overlay = i3Overlay;
    g_ctx = cl_ctx_init(basicI3ConfigFileName, &returnCode, i3Overlay->i3ProxyPort);

    //TODO check for return code

    clb = cl_alloc_buf(MAX_PACKET_SIZE);
}


/**
  * This function is called from the I3OverlayNetwork when it receives a
  * packet from the overlay.
  * The packet contents are not passed to this function; the function reads it in itself.
  */
void I3ClientContext::receivedPacketFromOverlay() {
    
    cl_trigger      *ctr;
    ctr = NULL;

    if (g_ctx == NULL) {
        
        I3_PRINT_DEBUG0(
            I3_DEBUG_LEVEL_MINIMAL,
            "\bError in cl_client_api:receivedPacket from Overlay - NULL context.\n"
        );
        
    } else {
        I3_PRINT_DEBUG0(
            I3_DEBUG_LEVEL_VERBOSE, 
            "Received Overlay Packet from I3Overlay\n"
        );
    
        int retVal = cl_process_recd_i3_pkt(g_ctx, clb);
	if (retVal == CL_RECV_ERROR) {
            I3_PRINT_DEBUG0 (
			DEBUG_LEVEL_MINIMAL,
			"There was an error while reading the received i3 packet.\n"
		);
            // There was an error reading the packet from its socket.
	    // Remove the tcp fd callback added by this overlay, if any.
            if (g_ctx->is_tcp) {
                int tmpTCPFd = getOverlayPktRcvTCPFD();
	    
		I3_PRINT_DEBUG1 (
				DEBUG_LEVEL_MINIMAL,
				"Remove the i3 TCP fd %d from the list of FD callbacks.  The i3 tcp fd is set to UNINITIALIZED.\n",
				tmpTCPFd	
			);
   
		// Remove the callback associated with this fd.
	    // If we don't remove this fd, the select() will give an error.     
		i3Overlay->delFDCallback(tmpTCPFd);

		g_ctx->tcp_fd = -1; // The TCP fd is invalid till it is initialized again.
		g_ctx->init_tcp_ctx_flag = 0;	// The TCP socket is not initialized.
            }
	}
    }
}


/**
  * This function returns the file descriptor of the socket over which overlay packets
  * are to be received.
  */
int I3ClientContext::getOverlayPktRcvFD() {
    return g_ctx->fd;
}

/**
  * This function returns the file descriptor of the tcp socket over which overlay packets
  * are to be received.
  */
int I3ClientContext::getOverlayPktRcvTCPFD() {
    return g_ctx->tcp_fd;
}


/**
  * This function is used to check if the client context is configured to
  * support TCP or not.
  */
bool I3ClientContext::isTCPSupported() {
    if (g_ctx->is_tcp) {
        return true;
    } else {
        return false;
    }
}


/**
  * This function is used to reinsert the specified trigger.
  */
void I3ClientContext::reInsertTrigger(I3Trigger *trig) {

    cl_ctx_insert_trigger(g_ctx, trig->cl_trig, 0);  //TODO verify
}

void I3ClientContext::sendToStack(i3_stack *stk, cl_buf* clb, uint8_t flags) {
    cl_ctx_send_stack(g_ctx, stk->ids, stk->len, clb,flags);
}


int I3ClientContext::insertTrigger(I3Trigger *trig, uint8_t flags)  {
    
    int retVal = cl_ctx_insert_trigger(g_ctx, trig->cl_trig, flags) ;
    
    // This callback is invoked when the challenge for this particular trigger is received.
    registerTriggerCallback( trig->cl_trig, CL_INTERNAL_HOOK_TRIGGER_ACK_TIMEOUT, dummy_add_ack_timeout, trig);
    registerTriggerCallback( trig->cl_trig, CL_INTERNAL_HOOK_TRIGGER_REFRESH_TIMEOUT, dummy_add_refresh_timeout, trig);

    
    // The following is code replicated from cl_ctx_insert_trigger() (API_INTERNAL)
    if (trig->cl_trig->type == CL_TRIGGER_I3) {
        //add a timeout for the challenge message.
        I3ObjectFunctor<I3ClientContext> *trigChallengeFunctor = new I3ObjectFunctor<I3ClientContext>(this, &I3ClientContext::timeoutAckInsert, (I3Trigger*) trig);
        I3TimerEvent *trigChallengeTimer = I3TimerEvent::create (ACK_TIMEOUT, random_sec(), (I3Functor *)trigChallengeFunctor, true);
        trig->timer = trigChallengeTimer;
        addTimerEvent(trigChallengeTimer);
    }
   
    
    if (trig->maxBW > 0) {

        // Do the rate limiting
        cl_ctx_trigger_ratelimit(g_ctx, trig->cl_trig, TOKEN_BUCKET_BYTE, 
		       1 * trig->maxBW * 1024 , /* token depth (in bytes) */
		       trig->maxBW  * 1024,  /* average rate (in bytes per second) */
		       1 * trig->maxBW  * 1024 /* peek rate (in bytes per second) --
			    * this parameter is ignored for now 
			    */
		       );
        PRINT_DEBUG1 (DEBUG_LEVEL_SUPER,
                "############Set rate limit for trigger (%d KBps).\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",
                trig->maxBW
            );
    }
    
    return retVal;
}

/**
  * This function is called when a ack (challenge) to a trigger insertion was not received on time.
  * This function closely resembles timeout_ack_insert() in i3_client_trigger.c, and hence the same name.
  * (API_INTERNAL)
  * @param trig The trigger for which challenge was not received.
  */
void I3ClientContext::timeoutAckInsert(I3Trigger* trig) {
    
    timeout_ack_insert(trig->cl_trig);
    trig->insertAttemptsCount++;
   
    if (trig->timer != NULL) {
        trig->timer->setDisabled();
        trig->timer = NULL;
    }
       
    if (trig->insertAttemptsCount > I3_MAX_NUM_TRIG_RETRIES) {

        I3_PRINT_INFO1 (
                    I3_INFO_LEVEL_WARNING,
                    "Given up trying to insert trigger.  Tried %d times.\n",
                    trig->insertAttemptsCount
                );
       
        return;
    }
    
    //add a timeout for the challenge message
    I3ObjectFunctor<I3ClientContext> *trigChallengeFunctor = new I3ObjectFunctor<I3ClientContext>(this, &I3ClientContext::timeoutAckInsert, (I3Trigger*) trig);
    I3TimerEvent *trigChallengeTimer = I3TimerEvent::create (ACK_TIMEOUT, random_sec(), (I3Functor *)trigChallengeFunctor, true);
    trig->timer = trigChallengeTimer;
    addTimerEvent(trigChallengeTimer);
    
    I3_PRINT_INFO2(
            I3_INFO_LEVEL_WARNING, 
            "Trigger insertion (id:%s) failed. Retrying %d time\n", 
            sprintf_i3_id(tmpBuf, &trig->cl_trig->t->id),trig->insertAttemptsCount);

}

void I3ClientContext::doAddressChangeCheck() {
  
    int originalUDP_fd = getOverlayPktRcvFD();
    int originalTCP_fd = getOverlayPktRcvTCPFD();
    
    timeout_address_change(g_ctx);
   

    if (originalUDP_fd != getOverlayPktRcvFD()) {
        // The UDP socket has changed.
        i3Overlay->delFDCallback (originalUDP_fd);
    
        //add the file descriptor for receiving I3 packets to the Proxy's core Select.
        TObjectFunctor<I3ClientContext> *fdfunc = new TObjectFunctor<I3ClientContext>(this, &I3ClientContext::receivedPacketFromOverlay);
        i3Overlay->addFDCallback(getOverlayPktRcvFD(), fdfunc, true);
    }

    if (originalTCP_fd != getOverlayPktRcvFD()) {
        
        i3Overlay->delFDCallback(originalTCP_fd);
        // TCP socket has changed.
        i3Overlay->addedTCPFDFlag = false;
        if (isTCPSupported() && isTCPInitOk()) {
        
            TObjectFunctor<I3ClientContext> *fdfunc = new TObjectFunctor<I3ClientContext>(this, &I3ClientContext::receivedPacketFromOverlay);
            i3Overlay->addFDCallback(getOverlayPktRcvTCPFD(), fdfunc, true);
            i3Overlay->addedTCPFDFlag = true;
        }
    }
    
}

void I3ClientContext::doServerUpdate() {
    timeout_server_update(g_ctx);

    if ( ! i3Overlay->addedTCPFDFlag && isTCPInitOk() ) {
        // If the TCP FD was not previously added and
        // is now ready.
        TObjectFunctor<I3ClientContext> *fdfunc = new TObjectFunctor<I3ClientContext>(this, &I3ClientContext::receivedPacketFromOverlay);
        i3Overlay->addFDCallback(getOverlayPktRcvTCPFD(), fdfunc, true);
        i3Overlay->addedTCPFDFlag = true;

        I3_PRINT_DEBUG1(DEBUG_LEVEL_MINIMAL, 
                "Initialized TCP Socket fd %d\n", getOverlayPktRcvTCPFD());
    }
}


/**
  * Get the number of i3 servers currently known to the proxy.
  */
int I3ClientContext::getNumKnownI3Servers() {
    return g_ctx->num_servers;
}

cl_trigger *I3ClientContext::createTrigger(ID *id, uint16_t prefix_len, Key *key, uint8_t flags, int *rc) {

    return cl_ctx_create_trigger(g_ctx, id, prefix_len, key, flags, rc);

}

int I3ClientContext::registerTriggerCallback(cl_trigger *ctr, uint16_t cbk_type, 
        void (*fun)(cl_trigger*, void *data, void *fun_ctx), 
        void *fun_ctx) {

    return cl_ctx_register_trigger_callback(g_ctx, ctr, cbk_type, fun, fun_ctx);

}

int I3ClientContext::registerContextCallback(uint16_t cbk_type,
        void (*fun)(void *ctx_data, void *data, void *fun_ctx),
        void *fun_ctx) {

    return cl_ctx_register_callback(g_ctx, cbk_type, fun, fun_ctx);
}


/**
  * This function adds a timeout for an ack for the specified trigger t.
  */
void dummy_add_ack_timeout(cl_trigger *t, void *data, void* fun_ctx) {

    I3Trigger* trig = (I3Trigger *) fun_ctx;

    //Cancel the current timer.
    if (trig->timer != NULL) {
        trig->timer->setDisabled();
        trig->timer = NULL;
    }


    trig->i3Overlay->context->addTimeoutTriggerAck(trig);


}

void I3ClientContext::addTimeoutTriggerAck(I3Trigger* trig) {
    //add the timeout_ack_refresh as in process_trigger_option(), case I3_OPT_CHALLENGE.
    I3ObjectFunctor<I3ClientContext> *trigAckRefreshFunctor = new I3ObjectFunctor<I3ClientContext>(this, &I3ClientContext::timeoutAckRefresh, (I3Trigger*) trig);
    I3TimerEvent *trigAckRefreshTimer = I3TimerEvent::create (ACK_TIMEOUT, random_sec(), (I3Functor *)trigAckRefreshFunctor, true);
    trig->timer = trigAckRefreshTimer;
    addTimerEvent(trigAckRefreshTimer);
}

/** This function just calls the original API's timeout_ack_refresh function.
  * The original function adds new timers through callbacks.
  */
void I3ClientContext::timeoutAckRefresh(I3Trigger* trig) {

    if (trig != NULL && trig->timer != NULL) {
        trig->timer = NULL;
    }
    
    timeout_ack_refresh(trig->cl_trig);
 
}

/**
  * This function is used to add timeout for refreshing the trigger.
  */
void dummy_add_refresh_timeout(cl_trigger *t, void *data, void* fun_ctx) {
    I3Trigger* trig = (I3Trigger *) fun_ctx;

    //Cancel the current timer.
    if (trig->timer != NULL) {
        trig->timer->setDisabled();
        trig->timer = NULL;
    }

    trig->i3Overlay->context->addTimeoutTriggerRefresh(trig);

}

void I3ClientContext::addTimeoutTriggerRefresh(I3Trigger *trig) {
    
    //add the timeout_ack_refresh as in process_trigger_option(), case I3_OPT_CHALLENGE.
    I3ObjectFunctor<I3ClientContext> *trigAckRefreshFunctor = new I3ObjectFunctor<I3ClientContext>(this, &I3ClientContext::timeoutAckRefresh, (I3Trigger*) trig);
    I3TimerEvent *trigAckRefreshTimer = I3TimerEvent::create(TRIGGER_REFRESH_PERIOD - ACK_TIMEOUT * MAX_NUM_TRIG_RETRIES, random_sec(), (I3Functor *)trigAckRefreshFunctor, true);
    trig->timer = trigAckRefreshTimer;
    addTimerEvent(trigAckRefreshTimer);
}

void I3ClientContext::addTimerEvent(TimerEvent *tEvent) {

    i3Overlay->addTimerEvent(tEvent);
}

/**
 * Checks if pinging is used to choose the closest i3 servers.
 * @return true if pinging is being used, else returns false.
 */
bool I3ClientContext::usePing() {

    if (g_ctx->use_ping) {
        return true;
    } else {
	    return false;
    }
}

/**
  * Was the TCP to be used when UseTCP is set correctly initialized?
  */
bool I3ClientContext::isTCPInitOk() {
    if (g_ctx->init_tcp_ctx_flag) {
        return true;
    } else {
        return false;
    }
    
}


/**
  * Create a trigger id->stack
  */
cl_trigger* I3ClientContext::createTriggerStack (
            ID *id, uint16_t prefix_len, string stack, uint8_t flags, int *retCode) {

    StringTokenizer st = StringTokenizer (stack, ",");
    int stackLen = st.countTokens();
    ID tmpStack[I3_MAX_STACK_LEN];

    int stackIndex = 0;
    while (st.hasMoreTokens() && stackIndex < I3_MAX_STACK_LEN) {

        string tmpToken = st.nextToken();
        I3Id::convertStr2I3Id ( tmpToken.c_str(), &tmpStack[stackIndex]);

        //Make all the ids in the stack private. 
        //TODO QQQ:  Is this okay??
        cl_set_private_id (&tmpStack[stackIndex]);

        stackIndex++;
    }

    return cl_ctx_create_trigger_stack(g_ctx, id, prefix_len, tmpStack, 
				     stackLen, flags, retCode);
    
}

/**
  * Destroys the state associated with the specified trigger.
  */
void I3ClientContext::destroyTrigger (cl_trigger * t) {
    cl_ctx_destroy_trigger (g_ctx, t);
}

void I3ClientContext::closeAllSockets() {

	i3_close_all_sockets (g_ctx);
}
